package cn.jtfadmin.activiti.controller.rest;

import cn.jtfadmin.activiti.domain.dto.FormDTO;
import cn.jtfadmin.activiti.domain.dto.ProcessInstanceDTO;
import cn.jtfadmin.activiti.domain.dto.ProcessInstanceDetailDTO;
import cn.jtfadmin.activiti.service.ActFormService;
import cn.jtfadmin.activiti.service.ActModelService;
import cn.jtfadmin.base.lang.common.domain.dto.PageRDTO;
import cn.jtfadmin.base.lang.common.domain.dto.ResultDTO;
import cn.jtfadmin.base.lang.common.utils.RequestUtils;
import cn.jtfadmin.base.lang.enums.BooleanEnum;
import org.activiti.bpmn.model.*;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程实例接口api
 * @author jtf
 * @since 0.0.1
 */
@RestController
@Validated
public class ActivitiProcessInstanceController {

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private ActModelService actModelService;

    @Autowired
    protected ProcessEngine processEngine;

    @Autowired
    protected ActFormService actFormService;

    @Autowired
    protected IdentityService identityService;

    /**
     * 获取流程实例列表,调用位置:流程列表
     * @param modelId 流程模型
     * @param tenantId 租户id
     * @param processName 流程名称
     * @param pageNo 页码
     * @param pageSize 分页数量
     * @return
     */
    @GetMapping(value = "/activiti/processInstance/list")
    public ResultDTO<PageRDTO<ProcessInstanceDTO>> listProcessInstance(@RequestParam(required = false) String modelId,
                                                         @RequestParam @NotBlank String tenantId,
                                                         @RequestParam(required = false) String processName,
                                                         @RequestParam(required = false, defaultValue = "1") Integer pageNo,
                                                         @RequestParam(required = false, defaultValue = "10") Integer pageSize){

        List<String> deploymentIds = null;
        if(StringUtils.hasText(modelId)){
            deploymentIds = new ArrayList<>(actModelService.listDeploymentIdsByModelId(modelId));
        }

        long count = createProcessInstanceQuery(deploymentIds, tenantId, processName).count();
        List<ProcessInstance> processInstances = createProcessInstanceQuery(deploymentIds, tenantId, processName)
                .listPage((pageNo - 1) * pageSize, pageSize);
        List<ProcessInstanceDTO> collect = processInstances.stream().map(this::instanceToDTO).collect(Collectors.toList());
        return ResultDTO.successData(new PageRDTO<>(collect,count));
    }

    /**
     * 按照id获取流程实例
     * @param id id
     * @return 流程实例
     */
    @GetMapping(value = "/activiti/processInstance/findById")
    public ResultDTO<ProcessInstanceDTO> findById(@RequestParam String id){
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();

        return ResultDTO.successData(this.instanceToDTO(processInstance));
    }


    /**
     * 将流程实例转换为流程实例dto, 返回前端使用
     * @param t 流程实例
     * @return 流程实例dto
     * @see #listProcessInstance(String, String, String, Integer, Integer)
     */
    private ProcessInstanceDTO instanceToDTO(ProcessInstance t){
        return new ProcessInstanceDTO(
                t.getProcessDefinitionId(),
                t.getProcessDefinitionName(),
                t.getProcessDefinitionKey(),
                t.getDeploymentId(),
                t.getBusinessKey(),
                t.isSuspended()? BooleanEnum.TRUE:BooleanEnum.FALSE,
                t.getProcessVariables(),
                t.getTenantId(),
                t.getName(),
                t.getDescription(),
                t.getLocalizedName(),
                t.getLocalizedDescription(),
                t.getStartTime(),
                t.getStartUserId(),
                t.getId(),
                t.isEnded()?BooleanEnum.TRUE:BooleanEnum.FALSE
        );
    }
    /**
     * 修改流程实例名,调用位置:流程列表
     * @param id 进程实例id
     * @param name 名称
     * @return 无
     */
    @PostMapping(value = "/activiti/processInstance/updateName")
    public ResultDTO<String> updateProcessInstanceName(@RequestParam @NotBlank String id,
                                                       @RequestParam(required = false) String name){
        runtimeService.setProcessInstanceName(id, name);
        return ResultDTO.success();
    }

    /**
     * 删除流程实例,调用位置:流程列表
     * @param id id
     * @param reason 删除原因
     * @return
     */
    @PostMapping(value = "/activiti/processInstance/delete")
    public ResultDTO<String> deleteProcessInstance(@RequestParam @NotBlank String id,
                                                       @RequestParam(required = false) String reason){
        runtimeService.deleteProcessInstance(id, reason);
        return ResultDTO.success();
    }

    /**
     * 删除流程实例
     * @param ids 流程实例id,....
     * @return
     */
    @DeleteMapping(value = "/activiti/processInstance/deleteBatch")
    @Transactional
    public ResultDTO<String > deleteProcessInstanceBatch(@RequestParam @NotBlank String ids,  @RequestParam(required = false) String reason){
        Set<String> idSet = StringUtils.commaDelimitedListToSet(ids);
        for (String id : idSet) {
            deleteProcessInstance(id, reason);
        }
        return ResultDTO.success();
    }

    /**
     * 填充表单中的值
     * @param formDTO 表单
     * @param historicVariableInstances 历史表单变量
     * @see #getDetailInfo(String)
     */
    private void fillFormDateValue(FormDTO formDTO, List<HistoricVariableInstance> historicVariableInstances){
        if(formDTO.getFormProperties()!=null){
            if(StringUtils.hasText(formDTO.getTaskId())){
                historicVariableInstances =
                        historicVariableInstances.stream().filter(t->Objects.equals(t.getTaskId(), formDTO.getTaskId())).collect(Collectors.toList());
            }else {
                historicVariableInstances = historicVariableInstances.stream()
                        .filter(t->!StringUtils.hasText(t.getTaskId())).collect(Collectors.toList());
            }
            for (FormDTO.FormPropertyDTO formProperty : formDTO.getFormProperties()) {
                for (HistoricVariableInstance target : historicVariableInstances) {
                    if(Objects.equals(target.getVariableName(), formProperty.getId())){
                        String variableTypeName = target.getVariableTypeName();
                        if(variableTypeName.equals("date") && Objects.nonNull(target.getValue())){
                            formProperty.setValue(new SimpleDateFormat(formProperty.getDatePattern()).format(target.getValue()));
                        }
                        if(Objects.nonNull(target.getValue())){
                            formProperty.setValue(target.getValue().toString());
                        }
                    }
                }
            }
        }

    }


    /**
     * 获取流程实例明细,调用位置:流程里列表
     * @param id 流程实例id
     * @return 流程实例明细
     */
    @GetMapping(value = "/activiti/processInstance/getDetailInfo")
    public ResultDTO<ProcessInstanceDetailDTO> getDetailInfo(String id){
        // 流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
        if(processInstance == null){
            throw new IllegalArgumentException("流程已完成或已删除");
        }
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processInstance.getProcessDefinitionId()).singleResult();

        ProcessInstanceDetailDTO dto = new ProcessInstanceDetailDTO();
        dto.setProcessInstanceDTO(this.instanceToDTO(processInstance));
        List<HistoricVariableInstance> historicVariableInstances = historyService.createHistoricVariableInstanceQuery().processInstanceId(id).list();
        FormDTO startForm = actFormService.getStartFormInfoByDeploymentId(processInstance.getDeploymentId());
        fillFormDateValue(startForm, historicVariableInstances);
        // 此流程实例的所有历史任务
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery().processInstanceId(id).list();
        dto.setStartFormDTO(startForm);
        List<ProcessInstanceDetailDTO.TaskDTO> taskDTOList = new ArrayList<>(historicTaskInstances.size());
        dto.setTaskDTOList(taskDTOList);
        for (HistoricTaskInstance h : historicTaskInstances) {
            try{
                String taskDefinitionKey = h.getTaskDefinitionKey();
                FormDTO taskForm = actFormService.getTaskFormInfonByProcessDefinitionIdAndTaskDefinitionKey(processDefinition.getId(),
                        taskDefinitionKey, h.getExecutionId());
                if(taskForm != null){
                    taskForm.setTaskId(h.getId());
                }

               fillFormDateValue(taskForm, historicVariableInstances);
                    ProcessInstanceDetailDTO.TaskDTO taskDTO = new ProcessInstanceDetailDTO.TaskDTO(
                        h.getDeleteReason(),
                        h.getStartTime(),
                        h.getEndTime(),
                        h.getDurationInMillis(),
                        h.getWorkTimeInMillis(),
                        h.getClaimTime(),
                        h.getId(),
                        h.getName(),
                        h.getDescription(),
                        h.getPriority(),
                        h.getOwner(),
                        h.getAssignee(),
                        h.getProcessInstanceId(),
                        h.getExecutionId(),
                        h.getProcessDefinitionId(),
                        h.getCreateTime(),
                        h.getTaskDefinitionKey(),
                        h.getDueDate(),
                        h.getCategory(),
                        h.getParentTaskId(),
                        h.getTenantId(),
                        h.getFormKey(),
                        h.getTaskLocalVariables(),
                        h.getProcessVariables(),
                        taskForm
                );
                taskDTOList.add(taskDTO);
            }catch (Exception e){
                e.printStackTrace();
            }

        }
        return ResultDTO.successData(dto);
    }
    /**
     * 获取流程实例运行图片,调用位置:流程列表，返回内容为输入流图片
     * @param id 流程实例id
     */
    @GetMapping(value = "/activiti/processInstance/getFlowImg", produces = MediaType.IMAGE_PNG_VALUE)
    public void getFlowImg(@RequestParam String id) {
        HttpServletResponse response = RequestUtils.getResponse();
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");//在跨域下前端拿到回应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(id+".png", "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // ignore
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
        List<Task> activeTasks=taskService.createTaskQuery().processDefinitionId(processInstance.getProcessDefinitionId()).list();
        // 已执行的节点ID集合
        List<String> executedActivityIdList = new ArrayList();
        List<String> highLines = new ArrayList();
        List<String> highNodes = new ArrayList();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        for(Task tk:activeTasks){
            executedActivityIdList.add(tk.getTaskDefinitionKey());
            highLines.addAll(getHighLines(bpmnModel,tk.getTaskDefinitionKey()));
            highNodes.addAll(getHighNodes(bpmnModel,tk.getTaskDefinitionKey()));
        }
        ProcessEngineConfiguration processEngineConfiguration = processEngine.getProcessEngineConfiguration();


        ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();


        try(InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", highNodes,
                highLines,"宋体","宋体","宋体",null,1.0);
        ){
            StreamUtils.copy(imageStream, RequestUtils.getResponse().getOutputStream());
        }catch (IOException ex){
            throw new RuntimeException(ex);
        }
    }
    /**
     * 获取高亮的线
     * @param bpmnModel 模型
     * @param key 当前节点key
     * @return 高亮的线
     * @see #getFlowImg(String)
     */
    private Set<String> getHighLines(BpmnModel bpmnModel,String key){
        FlowNode fl=(FlowNode) bpmnModel.getFlowElement(key);
        List<SequenceFlow> pvmTransitions = fl.getIncomingFlows();
        Set highLines=new HashSet<>();
        for(SequenceFlow sf:pvmTransitions){
            highLines.add(sf.getId());
            if(StringUtils.hasText(sf.getSourceRef())){
                highLines.addAll(getHighLines(bpmnModel,sf.getSourceRef()));
            }
        }
        return highLines;
    }
    /**
     * 获取高亮的节点
     * @param bpmnModel 模型
     * @param key 当前节点key
     * @return #getFlowImg(String)
     */
    private Set<String> getHighNodes(BpmnModel bpmnModel,String key){
        FlowNode fl=(FlowNode) bpmnModel.getFlowElement(key);
        List<SequenceFlow> sequenceFlows = fl.getIncomingFlows();
        Set<String> highNodes=new HashSet<>();
        highNodes.add(key);
//        for(SequenceFlow sf:sequenceFlows){
//            if(StringUtils.hasText(sf.getSourceRef())){
//                highNodes.addAll(getHighNodes(bpmnModel,sf.getSourceRef()));
//            }
//        }
        return highNodes;
    }

    /**
     * 创建流程实例查询条件
     * @param deploymentIds 不是ids
     * @param tenantId 租户id
     * @param processName 流程名
     * @return 流程查询实例
     * @see #listProcessInstance(String, String, String, Integer, Integer)
     */
    private ProcessInstanceQuery createProcessInstanceQuery( List<String> deploymentIds, String tenantId, String processName){

        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        if(StringUtils.hasText(tenantId)){
            processInstanceQuery.processInstanceTenantId(tenantId);
        }
        if(StringUtils.hasText(processName)){
            processInstanceQuery.processDefinitionName(processName);
        }
        if(!CollectionUtils.isEmpty(deploymentIds)){
            processInstanceQuery.deploymentIdIn(deploymentIds);
        }
        return processInstanceQuery;
    }


}
